package com.nulstudio.emotion.text.service;

import com.nulstudio.emotion.text.config.NulSecurityConfig;
import com.nulstudio.emotion.text.entity.NulInvite;
import com.nulstudio.emotion.text.entity.NulProfile;
import com.nulstudio.emotion.text.entity.NulRole;
import com.nulstudio.emotion.text.entity.NulUserDetail;
import com.nulstudio.emotion.text.exception.NulException;
import com.nulstudio.emotion.text.exception.NulExceptionConstants;
import com.nulstudio.emotion.text.jwt.NulJwtToken;
import com.nulstudio.emotion.text.mapper.AccountMapper;
import com.nulstudio.emotion.text.mapper.AuthorityMapper;
import org.springframework.lang.NonNull;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;

import java.util.List;

/**
 * Service for account-related operations.
 * @author nullsty
 * @since 2.0
 */
@Service
public final class AccountService {

    /**
     * Mapper for account module.
     */
    @NonNull
    private final AccountMapper accountMapper;

    @NonNull
    private final AuthorityMapper authorityMapper;

    /**
     * Service for invite module.
     */
    @NonNull
    private final InviteService inviteService;

    /**
     * Inject mappers for this service.
     * @param accountMapper mapper for account module
     */
    public AccountService(@NonNull AccountMapper accountMapper, @NonNull InviteService inviteService,
                          @NonNull AuthorityMapper authorityMapper) {
        this.accountMapper = accountMapper;
        this.inviteService = inviteService;
        this.authorityMapper = authorityMapper;
    }

    /**
     * Login to the system, and return token.
     * @param userName username of the user, must be non-null
     * @param password BCrypt encrypted password, must be non-null
     * @return a new token for the user
     * @throws NulException when the user does not exist, or the password is not correct
     */
    @NonNull
    public NulJwtToken login(@NonNull String userName, @NonNull String password) throws NulException {
        final NulUserDetail userDetail = getUserByUserName(userName);
        final BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
        if (!encoder.matches(password, userDetail.getPassword())) {
            throw new NulException(NulExceptionConstants.WRONG_PASSWORD);
        }
        return NulJwtToken.generate(new NulJwtToken.NulJwtTokenProperties(userDetail.getUid()));
    }

    public void register(@NonNull String userName, @NonNull String name, @NonNull String password,
                         int gender, @NonNull String inviteCode) throws NulException {
        if (accountMapper.checkUserExistence(userName)) {
            throw new NulException(NulExceptionConstants.USER_ALREADY_EXIST);
        }
        final NulInvite invite = inviteService.getInvite(inviteCode);
        final String encryptedPassword = new BCryptPasswordEncoder().encode(password);
        final NulUserDetail userDetail = new NulUserDetail(gender, userName, name, encryptedPassword, invite.getRoleId());
        inviteService.checkInviteRole(invite);
        NulUserDetail.assertValidity(userDetail);
        if (accountMapper.insertUser(userDetail) == 0) {
            throw new NulException(NulExceptionConstants.INTERNAL_SERVER_ERROR);
        }
        inviteService.registerInvite(invite);
    }

    /**
     * Fetch user information by ID.
     * @param uid ID of the user
     * @return information of the user
     * @throws NulException when the user does not exist
     */
    @NonNull
    public NulUserDetail getUserById(int uid) throws NulException {
        final NulUserDetail userDetail = accountMapper.getUserById(uid);
        if (userDetail == null) {
            throw new NulException(NulExceptionConstants.USER_NOT_EXIST);
        }
        return userDetail;
    }

    /**
     * Fetch user information by username.
     * @param name username of the user, must be non-null
     * @return information of the user
     * @throws NulException when the user does not exist
     */
    @NonNull
    public NulUserDetail getUserByUserName(@NonNull String name) throws NulException {
        final NulUserDetail userDetail = accountMapper.getUserByUserName(name);
        if (userDetail == null) {
            throw new NulException(NulExceptionConstants.USER_NOT_EXIST);
        }
        return userDetail;
    }

    @NonNull
    public NulProfile getUserProfile() {
        final NulUserDetail userDetail = NulSecurityConfig.getContextUserDetail();
        final NulRole role = authorityMapper.getRoleById(userDetail.getRole());
        return new NulProfile(userDetail.getUid(), userDetail.getUserName(), userDetail.getName(), role.getName());
    }

    /**
     * Get a list of all users.
     * @return a list of all users
     */
    @NonNull
    public List<NulUserDetail> getUserList() {
        return accountMapper.getUserList();
    }

    /**
     * Update name of the current user.
     * @param name new name of the user, must be non-null
     */
    public void updateCurrentUserName(@NonNull String name) {
        final NulUserDetail userDetails = NulSecurityConfig.getContextUserDetail();
        final int result = accountMapper.updateName(userDetails.getUid(), name);
        if (result <= 0) {
            throw new NulException(NulExceptionConstants.USER_NOT_EXIST);
        }
    }
}
